﻿#region imports

using System;
using System.Collections.Specialized;
using System.Data.Services;
using System.Data.Services.Common;
using System.Data.Services.Providers;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Text;
using System.Web;
using Codeplex.ODataTFS.Model;
using Codeplex.ODataTFS.Model.Providers;
using Codeplex.ODataTFS.Model.Serialization;
using Codeplex.ODataTFS.Web.Infrastructure;
using Microsoft.Data.Services.Toolkit;

#endregion

namespace Codeplex.ODataTFS.Web
{
	[ServiceBehavior(IncludeExceptionDetailInFaults = false, InstanceContextMode = InstanceContextMode.PerSession)]
	public class TFSService : ODataService<TFSDataContext>
	{
		private static string _tfsUrl;

		private ICredentials TFSCredentials
		{
			get
			{
				var user = HttpContext.Current.User.Identity as TfsUser;
				if (user == null || string.IsNullOrWhiteSpace(user.Name) || string.IsNullOrWhiteSpace(user.Password))
				{
					return null;
				}

				return new NetworkCredential(user.Name, user.Password, user.Domain);
			}
		}

		public static void InitializeService(DataServiceConfiguration config)
		{
			config.SetEntitySetAccessRule("Builds", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("BuildDefinitions", EntitySetRights.All);
			config.SetEntitySetAccessRule("Changesets", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("Projects", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("WorkItems", EntitySetRights.All);
			config.SetEntitySetAccessRule("Attachments", EntitySetRights.All);
			config.SetEntitySetAccessRule("Changes", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("Queries", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("Branches", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("AreaPaths", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("Links", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("IterationPaths", EntitySetRights.AllRead);
			config.SetEntitySetAccessRule("Users", EntitySetRights.AllRead);

			config.SetServiceOperationAccessRule("TriggerBuild", ServiceOperationRights.All);

			config.SetEntitySetPageSize("Builds", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("BuildDefinitions", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("Changesets", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("Projects", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("WorkItems", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("Attachments", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("Changes", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("Queries", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("Branches", Constants.DefaultEntityPageSize);
			config.SetEntitySetPageSize("Links", Constants.DefaultEntityPageSize);

			config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;

			config.UseVerboseErrors = true;

			_tfsUrl = ConfigReader.GetConfigValue("ODataTFS.TfsServer").TrimEnd('/');
		}

		public override object GetService(Type serviceType)
		{
			if (serviceType == typeof (IDataServiceStreamProvider))
			{
				return new TFSStreamProvider(TFSCredentials);
			}

			return base.GetService(serviceType);
		}

		[WebInvoke(Method = "POST")]
		public void TriggerBuild()
		{
			var body = OperationContext.Current.RequestContext.RequestMessage.GetReaderAtBodyContents();
			if (body.Read())
			{
				string decodedBodyString = new string(Encoding.UTF8.GetChars(body.ReadContentAsBase64()));

				NameValueCollection args = HttpUtility.ParseQueryString(decodedBodyString);

				string project = args["project"];
				string definition = args["definition"];

				CurrentDataSource.TriggerBuild(project, definition);
			}
			else
			{
				//TODO
			}
		}

		protected override TFSDataContext CreateDataSource()
		{
			Message request = OperationContext.Current.RequestContext.RequestMessage;
			UriTemplateMatch uriMatch = (UriTemplateMatch) request.Properties["UriTemplateMatchResults"];
			string collection = null;
			if (uriMatch.RelativePathSegments.Count > 0)
			{
				collection = uriMatch.RelativePathSegments[0];
				request.Properties["MicrosoftDataServicesRootUri"] = new Uri(uriMatch.BaseUri, collection);
			}

			if (string.IsNullOrEmpty(collection))
			{
				throw new DataServiceException(400, "Bad Request", "You need to specify the name of a TFS Project Collection as the first part of the URL.", "en-us", null);
			}

			string tfsCollectionUri = string.Format("{0}/{1}", _tfsUrl, collection);

			TFSDataContext tfsDataContext = new TFSDataContext(new TFSProxyFactory(new Uri(tfsCollectionUri, UriKind.Absolute), TFSCredentials));

			return tfsDataContext;
		}

		protected override void HandleException(HandleExceptionArgs args)
		{
			if (args.Exception is DataServiceException && ((DataServiceException) args.Exception).StatusCode == 500)
			{
				ValidateTeamProjectCollectionAndAuthorization();
			}

			if ((args.Exception != null))
			{
				Trace.TraceError(args.Exception.ToString());
			}

			base.HandleException(args);
		}

		private void ValidateTeamProjectCollectionAndAuthorization()
		{
			var collection = OperationContext.Current.IncomingMessageProperties["TfsCollectionName"] as string;
			var tfs = _tfsUrl;
			var collectionUri = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}/{1}", tfs, collection));

			if (TFSCredentials != null)
			{
				bool isAuthorized;
				bool collectionExists = TFSBaseProxy.CollectionExists(collectionUri, TFSCredentials, out isAuthorized);
				if (!collectionExists)
				{
					throw new DataServiceException(404, "Not found", string.Format(CultureInfo.InvariantCulture, "The TFS Project Collection named {0} was not found.", collection), "en-US", null);
				}

				if (!isAuthorized)
				{
					SendAuthHeader();
					throw new DataServiceException(401, "Not authorized", string.Format(CultureInfo.InvariantCulture, "You are not authorized to view the TFS Project Collection named {0}.", collection), "en-US", null);
				}
			}
		}

		private void SendAuthHeader()
		{
			var context = HttpContext.Current;
			var tfsServerUri = new Uri(ConfigReader.GetConfigValue("ODataTFS.TfsServer"), UriKind.Absolute);

			context.Response.Clear();
			context.Response.ContentType = "application/xml";
			context.Response.StatusCode = 401;
			context.Response.StatusDescription = "Unauthorized";
			context.Response.AddHeader("WWW-Authenticate", string.Format(CultureInfo.InvariantCulture, "Basic realm=\"{0}\"", tfsServerUri.AbsoluteUri));
			context.Response.AddHeader("DataServiceVersion", "1.0");
			context.Response.Write(@"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes"" ?>
                                     <error xmlns=""http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"">
                                         <code>Not authorized</code>
                                         <message xml:lang=""en-US"">Please provide valid TFS credentials (domain\\username and password)</message>
                                     </error>");
			context.Response.End();
		}
	}
}
